using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Html;
using System.Runtime.CompilerServices;
using jQueryApi;

namespace Awaiter
{
    public partial class Await
    {
        public Await Que(AwaitDelegate fn)
        {
            if (_parallelActions == null)
                _parallelActions = new List<AwaitDelegate>();
            _parallelActions.Add(
                delegate(Await aw)
                {
                    LogDoFn(fn);
                    if (PassExceptions)
                    {
                        fn(aw);
                        return;
                    }
                    try
                    {
                        fn(aw);
                    }
                    catch (Exception ex)
                    {
                        Handle(ex);
                    }
                });
            _count++;
            _queCount++;
            return this;
        }

        public Await AddDl(AwaitDelegate fn)
        {
            if (_serialSaved == null)
                _serialSaved = new List<AwaitDelegate>();
            _serialSaved.Add(
                delegate(Await aw)
                {
                    LogDoFn(fn);
                    if (PassExceptions)
                    {
                        fn(aw);
                        return;
                    }
                    try
                    {
                        fn(aw);
                    }
                    catch (Exception ex)
                    {
                        Handle(ex);
                    }
                });
            _count++;
            return this;
        }

        public Await AddDx(AwaitDelegate fn)
        {
            if (_serialSaved == null)
                _serialSaved = new List<AwaitDelegate>();
            _serialSaved.Add(
                delegate(Await aw)
                {
                    LogDoFn(fn);
                    if (PassExceptions)
                    {
                        fn(aw);
                        aw.Done();
                        return;
                    }
                    try
                    {
                        fn(aw);
                        aw.Done();
                    }
                    catch (Exception ex)
                    {
                        Handle(ex);
                    }
                });
            _count++;
            return this;
        }

        [AlternateSignature]
        public extern Await AddAw(Action<Await, AwaitBoolDelegateObject> fn, AwaitBoolDelegateObject arg1);

        [AlternateSignature]
        public extern Await AddAw(Action<Await> fn);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, int> fn, int arg1);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, Dictionary> fn, Dictionary arg1);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, bool> fn, bool arg1);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string> fn, string arg1);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, object> fn, object arg1);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, Await> fn, Await arg1);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, jQueryObject> fn, jQueryObject arg1);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, jQueryObject, int> fn, jQueryObject arg1, int arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, object, object> fn, object arg1, object arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, object, Dictionary> fn, object arg1, Dictionary arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, Dictionary, jQueryObject> fn, Dictionary arg1, jQueryObject arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, object> fn, string arg1, object arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, int> fn, string arg1, int arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, int, int> fn, string arg1, int arg2, int arg3);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, bool> fn, string arg1, bool arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, jQueryObject> fn, string arg1, jQueryObject arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, string> fn, string arg1, string arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, string, string> fn, string arg1, string arg2, string arg3);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, string, object> fn, string arg1, string arg2, object arg3);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, string, Action<object>> fn, string arg1, string arg2, Action<object> arg3);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, string, string, Action<object, object[]>> fn, string arg1, string arg2, Action<object, object[]> arg3);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, List<object>> fn, List<object> arg1);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, List<object>, string> fn, List<object> arg1, string arg2);

        [AlternateSignature]
        public extern Await AddAw(Action<Await, List<object>, string, object> fn, List<object> arg1, string arg2, object arg3);

        public Await AddAw(Action<Await, object, object, object> fn, object arg1, object arg2, object arg3)
        {
            if (_serialSaved == null)
                _serialSaved = new List<AwaitDelegate>();
            _serialSaved.Add(
                delegate(Await aw)
                {
                    LogDoFn(fn, arg1, arg2, arg3);
                    if (PassExceptions)
                    {
                        fn(aw, arg1, arg2, arg3);
                        return;
                    }
                    try
                    {
                        fn(aw, arg1, arg2, arg3);
                    }
                    catch (Exception ex)
                    {
                        Handle(ex);
                    }
                });
            _count++;
            return this;
        }

        public Await QueFn(Action fn)
        {
            Que(
                delegate(Await aw)
                {
                    LogDoFn(fn);
                    if (PassExceptions)
                    {
                        fn();
                        aw.Done();
                        return;
                    }
                    try
                    {
                        fn();
                        aw.Done();
                    }
                    catch (Exception ex)
                    {
                        Handle(ex);
                    }
                });
            return this;
        }

        [AlternateSignature]
        public extern Await AddFn(Action fn);

        [AlternateSignature]
        public extern Await AddFn(Action<int> fn, int arg1);

        [AlternateSignature]
        public extern Await AddFn(Action<bool> fn, bool arg1);

        [AlternateSignature]
        public extern Await AddFn(Action<string> fn, string arg1);

        [AlternateSignature]
        public extern Await AddFn(Action<string, string> fn, string arg1, string arg2);

        [AlternateSignature]
        public extern Await AddFn(Action<object> fn, object arg1);

        [AlternateSignature]
        public extern Await AddFn(Action<jQueryObject> fn, jQueryObject arg1);

        public Await AddFn(Action<object, object> fn, object arg1, object arg2)
        {
            if (_serialSaved == null)
                _serialSaved = new List<AwaitDelegate>();
            _serialSaved.Add(
                delegate(Await aw)
                {
                    LogDoFn(fn, arg1, arg2);
                    if (PassExceptions)
                    {
                        fn(arg1, arg2);
                        aw.Done();
                        return;
                    }
                    try
                    {
                        fn(arg1, arg2);
                        aw.Done();
                    }
                    catch (Exception ex)
                    {
                        Handle(ex);
                    }
                });
            _count++;
            return this;
        }

        //[AlternateSignature]
        //public extern Await SetResult(List<object> ptr);

        //[AlternateSignature]
        //public extern Await SetResult(List<object> ptr, string excep);

        //public Await SetResult(List<object> ptr, string excep, object deef)
        //{
        //    deef = Arguments.Length > 2 ? deef : ptr[0];
        //    if (Arguments.Length > 1)
        //        HandleDl(excep, delegate
        //        {
        //            Result = deef;
        //            Done();
        //        });
        //    AddDx(delegate { ptr[0] = Result ?? deef; });
        //    return this;
        //}

        public Await AddEv(string name, jQueryEventHandler fn)
        {
            if (Events == null)
                Events = new Dictionary<string, jQueryEventHandler>();
            Events[name] = fn;
            return this;
        }

        public void DoneWith(string n, object v)
        {
            Vars[n] = v;
            Done();
        }


        public Await Done()
        {
            if (_ignore)
                return this;
            if (_abort)
                return DoAbort();
            if (_traceList.Count > 0)
            {
                string msg = _traceList[_traceList.Count - 1];
                _traceList.RemoveAt(_traceList.Count - 1);
                Trace("Done: " + msg);
            }
            _count--;
            _queCount--;
            return Next();
        }

        public Await InsertDl(AwaitDelegate fn)
        {
            if (!_abort)
            {
                Debug.Assert(fn != null, "InsertDl fn != null");
                _serialActions.Insert(0, fn);
                _count++;
            }
            return this;
        }

        public Await Always(AwaitDelegate fn)
        {
            _alwaysAction = fn;
            return this;
        }

        /// <summary>
        /// Handles the specified exception.
        /// </summary>
        /// <param name="exceptionName">Name of the exception.</param>
        /// <param name="fn">The action to perform. Must envoke Done.</param>
        /// <returns>this</returns>
        public Await HandleDl(string exceptionName, AwaitDelegate fn)
        {
            if (_exceptionHandlers == null)
                _exceptionHandlers = new Dictionary<string, AwaitDelegate>();
            _exceptionHandlers[exceptionName] = fn;
            return this;
        }

        /// <summary>
        /// Handles the specified exception with automatic Done() being done at the end
        /// </summary>
        /// <param name="exceptionName">Name of the exception.</param>
        /// <param name="fn">The action to perform.</param>
        /// <returns>this</returns>
        public Await HandleDx(string exceptionName, AwaitDelegate fn)
        {
            return HandleDl(exceptionName,
                            delegate
                            {
                                fn(this);
                                Done();
                            });
        }

        public Await Abort()
        {
            _abort = true;
            return DoAbort();
        }

        public Await Sleep(int ms)
        {
            return AddDl(delegate { AwaitTimer.SetTimeout(delegate { Done(); }, ms); });
        }

        /// <summary>
        /// Waits until a wait target object is satisfied.
        /// </summary>
        /// <param name="awp">The awp.</param>
        /// <param name="waitTarget">The wait target object</param>
        private void WaitAw(Await awp, object waitTarget)
        {
            WaitTarget t = (WaitTarget) waitTarget;
            t.TargetMs = Script.Value(t.TargetMs, (new Date()).GetTime() + 10000);
            if (t.Fn())
            {
                awp.Done();
                return;
            }
            if ((new Date()).GetTime() >= t.TargetMs)
            {
                Log(String.Format("WaitAw waited {0} for {1}", t.TargetMs, t.Fn));
                awp.Done();
                return;
            }
            AwaitTimer.SetTimeout(delegate { WaitAw(awp, t); }, 13);
        }

        [AlternateSignature]
        public extern Await WaitDx(AwaitBoolDelegate fn);

        public Await WaitDx(AwaitBoolDelegate fn, int maxWaitMs)
        {
            WaitTarget t = new WaitTarget();
            t.Fn = fn;
            t.TargetMs = (new Date()).GetTime() + Script.Value(maxWaitMs, 10000);
            return AddAw(WaitAw, t);
        }

        public Await SimulateAsync()
        {
            return AddDl(delegate { AwaitTimer.SetTimeout(delegate { Done(); }, SimulatedLatency); });
        }

        [AlternateSignature]
        public virtual extern Await Commit();

        public virtual Await Commit(Await awp)
        {
            AwaitTimer.ClearAll();
            _ignore = _abort = false;
            _awp = Script.IsValue(awp) ? awp : null;
            if (_serialSaved != null)
                _serialActions = _serialSaved.Clone();
            _queCount = _parallelActions == null ? 0 : _parallelActions.Count;
            _count = _serialActions == null ? 0 : _serialActions.Count + _queCount;
            return DoActions();
        }

        private Await DoActions()
        {
            if (_queCount > 0)
                foreach (AwaitDelegate f in _parallelActions)
                {
                    if (_abort)
                        return DoAbort();
                    ActionClosure(f);
                }
            else
                Next();
            return this;
        }

        private Await Next()
        {
            if (_ignore)
                return this;
            if (_abort)
                return DoAbort();
            Await my = this;
            if (_queCount <= 0 && _count > 0)
            {
                AwaitDelegate f = _serialActions[0];
                _serialActions.RemoveAt(0);
                AwaitTimer.SetTimeout(delegate { f(my); }, 1);
                return this;
            }
            if (_count <= 0)
                Finish();
            return this;
        }

        public void Finish()
        {
            _ignore = true;
            DoAlways(
                delegate
                {
                    if (Script.IsValue(_awp))
                    {
                        ProxyVars();
                        _awp.Done();
                    }
                });
        }

        [AlternateSignature]
        public extern Await LoadAndDo(string url, string className);

        [AlternateSignature]
        public extern Await LoadAndDo(string url, string className, Dictionary opts);

        public Await LoadAndDo(string url, string className, Dictionary opts, jQueryEvent e)
        {
            opts = opts ?? new Dictionary();
            LoadScript(url);
            AddDx(delegate
                  {
                      try
                      {
                          string[] p = className.Split(".");
                          Action<jQueryEvent, Dictionary> fn = (Action<jQueryEvent, Dictionary>) Script.Literal("window[{0}][{1}]", p[0], p[1]);
                          fn(e, opts);
                      }
                      catch (Exception ex)
                      {
                          Error(ex.ToString());
                      }
                  });
            return this;
        }

        [AlternateSignature]
        public extern Await LoadScript(string url);

        public Await LoadScript(string url, Action<Await> fn)
        {
            if (url == null)
                return this;
            url = url.Trim();
            AddDl(
                delegate
                {
                    if (!Scripts.ContainsKey(url))
                    {
                        //Log(String.Format("Requesting script {0}", url));
                        jQueryAjaxOptions options = new jQueryAjaxOptions();
                        options.Url = url;
                        options.DataType = "script";
                        options.Success =
                            delegate { Scripts[url] = true; };
                        options.Error =
                            delegate(jQueryXmlHttpRequest request, string textStatus, Exception error)
                            {
                                string err = String.Format("LoadScript() {0}, {1}, {2}", url, textStatus, error);
                                Error(err);
                                Handle(new Exception("Blob Load Error: " + err));
                            };
                        jQuery.Ajax(options);
                        Scripts[url] = false;
                        LoadScriptWait(url);
                    }
                    else if (!Scripts[url])
                        LoadScriptWait(url);
                    else
                        Done();
                });

            if (Arguments.Length > 1)
                AddAw(fn);
            return this;
        }

        public Await LoadJson(string name, string url)
        {
            return AddDl(
                delegate
                {
                    jQueryAjaxOptions options = new jQueryAjaxOptions();
                    options.Url = url;
                    options.DataType = "json";
                    options.Type = "GET";
#if DEBUG
                    options.Cache = false;
#endif

                    options.Success =
                        delegate(object data, string textStatus, jQueryXmlHttpRequest request1)
                        {
                            if (name != null)
                                Files[name] = data;
                            Done();
                        };

                    options.Error =
                        delegate(jQueryXmlHttpRequest request, string textStatus, Exception error)
                        {
                            if (name != null)
                                Files[name] = null;
                            string err = String.Format("LoadJson() {0}, {1}, {2}", url, textStatus, error);
                            Error(err);
                            Handle(new Exception("Blob Load Error: " + err));
                        };
                    jQuery.Ajax(options);
                }
                );
        }

        public Await LoadText(string name, string url)
        {
            return AddDl(
                delegate
                {
                    jQueryAjaxOptions options = new jQueryAjaxOptions();
                    options.Url = url;
                    options.DataType = "html";
                    options.Type = "GET";
#if DEBUG
                    options.Cache = false;
#endif

                    options.Success =
                        delegate(object data, string textStatus, jQueryXmlHttpRequest request1)
                        {
                            Files[name] = data;
                            Done();
                        };

                    options.Error =
                        delegate(jQueryXmlHttpRequest request, string textStatus, Exception error)
                        {
                            string err = String.Format("LoadText() {0}, {1}, {2}", url, textStatus, error);
                            Error(err);
                            Handle(new Exception("Blob Load Error: " + err));
                        };
                    jQuery.Ajax(options);
                }
                );
        }

        public Await LoadCss(string url)
        {
            return AddDl(
                delegate
                {
                    if (Files.ContainsKey(url))
                    {
                        Done();
                        return;
                    }

                    jQueryObject css = jQuery.FromHtml("<link/>")
                        .Attribute(
                            new Dictionary(
                                "rel", "stylesheet",
                                "type", "text/css",
                                "href", url
                                )
                        )
                        ;

                    css.AppendTo("head");

                    if (Type.HasField(Window.Document, "styleSheets"))
                    {
                        int mark = (new Date()).GetTime() + _maxWaitStyleSheet;

                        int tmr = 0;
                        tmr = AwaitTimer.SetInterval(
                            delegate
                            {
                                DynamicStyleSheet sheet = HasStyleSheet(url);
                                if (sheet != null || (new Date()).GetTime() > mark)
                                {
                                    // ReSharper disable AccessToModifiedClosure
                                    AwaitTimer.ClearTimer(tmr);
                                    // ReSharper restore AccessToModifiedClosure
                                    Files[url] = sheet;
                                    Done();
                                }
                            }, 5
                            );
                    }
                    else
                    {
                        // For older browsers just wait a bit and hope it loaded
                        AwaitTimer.SetTimeout(delegate { Done(); }, 250);
                    }
                }
                );
        }

        private DynamicStyleSheet HasStyleSheet(string url)
        {
            DynamicStyleSheet[] stylesheets = (DynamicStyleSheet[]) Type.GetField(Window.Document, "styleSheets");
            foreach (DynamicStyleSheet sheet in stylesheets)
            {
                if (!String.IsNullOrEmpty(sheet.href) && sheet.href.IndexOf(url) >= 0)
                    return sheet;
            }
            return null;
        }
    }

    [Imported]
    public sealed class DynamicStyleSheet
    {
        public Element[] cssRules;
        public string href;
        public int length;
        public Element[] rules;
        public string title;
    }
}